const http = require('http');
const config = require('./config/index.js');
const fs = require('fs');
const path = require('path');
const url = require('url');
const zlib = require('zlib');

class LLZSServer{
    /**
     * @param {*} props 一些用于初始化Server的值
     */
    constructor(props) {
        this.config = {...config, ...props}; 
    }

    

    /**
     * 设置http模块的回调函数
     * @param {*} req node http模块req对象 
     * @param {*} res node http模块res对象
     */
    async handleRequest(req, res) {
        let {dir} = this.config;
        dir = path.resolve(dir);
        
        /**
         * 处理URL的路径
         * 并且拦截favicon.ico文件的请求
         */
        let _url = {};
        _url = url.parse(req.url);
        _url.pathname = decodeURIComponent(_url.pathname);

        if (_url.pathname==='/favicon.ico'){return res.end();}
        
        /**
         * 如果是本地文件，则直接响应文件本身
         * 如果是一个目录，响应此目录下的所有文件形成的列表页面
         */
        try{
            const file = path.normalize(dir + _url.pathname);
            const statObj = fs.statSync(file);


            if (statObj.isDirectory()) {
                res.setHeader('Content-Type','text/html;charset=utf8');

                let _str = 'Here is the file list <br/>';
                const _dirs = fs.readdirSync(file);
                _dirs.forEach(function(item) {
                    if (_url.pathname === '/') {
                        _url.pathname = '';
                    }

                    _str += `<h5>
                        <a href="${_url.pathname + '/' + item}">
                            ${item}
                        </a>
                    </h5>`;
                });

                res.end(_str);
            } else{
                this.sendFile(req, res, statObj, file);
            }


        }catch (e) {
            console.log(e);
            this.sendError(req,res);
        }
    }

    //-----------------------内部工具方法-----------------------
    /**
     * 根据缓存的配置文件来进行初始化该请求的响应头
     * 只使用对比缓存，不使用强制缓存
     * 根据文件是否变化来进行缓存
     * @param {*} res 
     * @param {*} stat 
     */
    initCache(res , stat) {
        const {lastModified, etag} = this.config.cache;

        if(lastModified) {
            res.setHeader('Last-Modified', stat.mtime.toUTCString());//stat.mtime表示修改时间
        }
        if(etag) {  
            const mtime = stat.mtime.getTime().toString(16);
            const size = stat.size.toString(16);
            res.setHeader('ETag', `W/"${size}-${mtime}"`);
        }
    }

    /**
     * 判断发送http请求的客户端的最新缓存是否存在，存在返回true，不存在返回false
     * @param {*} stat 目标文件的信息
     * @param {*} req  客户端的请求, 可能有缓存的信息，需要进行对照，观察缓存是否失效了
     * @param {*} res  服务器的响应, 如果对应的缓存过期了,设置新的缓存的请求头
     * @returns 
     */
    flashCache(stat, req, res) {    
        this.initCache(res, stat);
    
        /**
         * 判断请求是否为第一次请求
         */
        const lastModified = req.headers['if-modified-since'];
        const etag = req.headers['if-none-match'];
        if(!lastModified && !etag) {
            return false;
        }


        /**
         * 判断请求的文件是否过期
         */
        // 如果有lastModified,并且与设置的最新lastModified不一样，说明也过期了
        if(lastModified && lastModified !== res.getHeader('Last-Modified')) {
            return false;
        }
        //检测etag
        if(etag && etag !== res.getHeader('ETag')) {
            return false;
        }
        return true;
    }

    /**
     * 判断支持哪种压缩模式, 
     * 如果支持压缩模式，则返回一个对象{name:'压缩模式',stream:'zlib对应的模式'}
     * 如果不支持模式，返回false
     * @param {*} req node http模块req对象, 用于去判断用户的游览器支持哪种压缩格式, 然后在后台进行压缩
     */
    zip(req) {
        let _encoding = req.headers["accept-encoding"];
        let type = {};

        if(_encoding && /gzip/.test(_encoding)) {
            type['stream'] = zlib.createGzip();
            type['name'] = 'gzip';
        }else if(_encoding && /deflate/.test(_encoding)) {
            type['stream'] = zlib.createDeflate();
            type['name'] = 'deflate';
        }else {
            return false;
        }

        return type;
    }


    /**
     * 作用:响应文件给用户, 对缓存相关的请求头和解码相关的请求头有一些操作
     * @param {*} req node http模块req对象 
     * @param {*} res node http模块res对象
     * @param {*} statObj 文件的基本信息,用于生成缓存
     * @param {*} filePath 在当前操作系统里文件的路径
     */
    sendFile(req, res, statObj, filePath) {
        
        if (this.flashCache(statObj, req, res)){
            res.statusCode = 304;
            res.end();
            return;
        }

        let _fileStream = fs.createReadStream(filePath);
        let _zipMode = this.zip(req); 

        

        if(_zipMode) {
            res.setHeader("Content-Encoding", _zipMode['name']);
            _fileStream.pipe(_zipMode['stream']).pipe(res);
            return;
        }else {
            _fileStream.pipe(res);
            return;
        }


    }
    
    
    
    /**
     * 调用此方法，用于发送错误信息响应给用户
     * @param {*} req node http模块req对象 
     * @param {*} res node http模块res对象
     * 支持在config里配置自定义错误信息
     */
    sendError(req, res) {
        res.statusCode = 404;
        if (this.config.errorMsg) {
            res.writeHead(200, {'Content-Type': 'text/html;charset=UTF-8'});
            res.write(this.config.errorMsg);
            res.end();
        } else {
            res.end('404 Not Fonud');
        }
    }

    //-----------------------外部方法---------------------------
    /**
     * 调用此方法，用于启动服务器
     * @param {*} callback 在监听的时候执行的函数
     */
    start(callback) {
        const _server = http.createServer(this.handleRequest.bind(this));

        _server.listen(this.config.port, this.config.localhost,function() {
            if (callback instanceof Function) {
                callback();
            }
        });
    }

}

module.exports = LLZSServer;